﻿using System;
using System.Collections.Generic;

using System.Text;
using System.IO;

namespace rkShared.BEncode
{
    public class BEncodedDocument
    {
        public IList<IBElement> Elements { get; private set; }
        public BEncodedDocument()
        {
            this.Elements = new List<IBElement>();
        }
        public void LoadDocument(string fileName)
        {
            using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                BinaryReader _reader = new BinaryReader(stream, Encoding.ASCII);
                while (_reader.PeekChar() != -1)
                {
                    this.Elements.Add(DecodeBElement(_reader));
                }
            }
        }
        public void SaveDocument(string fileName)
        {
            using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                BinaryWriter _writer = new BinaryWriter(stream, Encoding.ASCII);
                foreach (IBElement element in this.Elements)
                    element.Encode(_writer);

                _writer.Flush();
            }
        }
        public static IBElement DecodeBElement(BinaryReader reader)
        {
            char _ch = reader.ReadChar();
            switch (_ch)
            {
                case 'i':
                    return DecodeInt(reader);
                case 'l':
                    return DecodeList(reader);
                case 'd':
                    return DecodeDictioanary(reader);
                default:
                    byte[] _buffer = ReadUntil(reader, ':');
                    string _length = _ch + Encoding.ASCII.GetString(_buffer);
                    return DecodeString(reader, int.Parse(_length));
            }
            throw new Exception("Unexpected value while decoding " + _ch);
        }
        internal static IBElement DecodeString(BinaryReader reader, int length)
        {
            byte[] _buffer = new byte[length];
            int _bytesRead = reader.Read(_buffer, 0, _buffer.Length);
            if (_bytesRead != length)
                throw new Exception("Unexpected end of file");

            BEncodedString _string = new BEncodedString();
            _string.Decode(_buffer);
            return _string;
        }
        internal static IBElement DecodeDictioanary(BinaryReader reader)
        {
            BEncodedDictionary _result = new BEncodedDictionary();
            while (reader.PeekChar() != 'e')
            {
                BEncodedString _key = DecodeBElement(reader) as BEncodedString;
                if (_key != null)
                {
                    IBElement _value = DecodeBElement(reader);
                    _result[Encoding.UTF8.GetString(_key.Value)] = _value;
                }
                else
                {
                    throw new Exception("Invalid dictionary key type, expecting BEncodedString found: " + _key.GetType().Name);
                }
            }
            reader.ReadChar();
            return _result;
        }
        internal static IBElement DecodeList(BinaryReader reader)
        {
            BEncodedList _result = new BEncodedList();
            while (reader.PeekChar() != 'e')
                _result.Add(DecodeBElement(reader));
            reader.ReadChar();
            return _result;

        }
        internal static IBElement DecodeInt(BinaryReader reader)
        {
            byte[] _intBytes = ReadUntil(reader, 'e');
            BEncodedInt _int = new BEncodedInt();
            _int.Decode(_intBytes);
            return _int;

        }
        internal static byte[] ReadUntil(BinaryReader reader, char c)
        {
            byte[] _result = null;
            using (MemoryStream stream = new MemoryStream())
            {
                while (reader.PeekChar() != -1 && reader.PeekChar() != c)
                {
                    stream.Write(new byte[] { reader.ReadByte() }, 0, 1);
                }
                if (reader.PeekChar() == -1)
                    throw new Exception("Unexpected end of file, while trying to read until " + c);
                reader.ReadChar();
                _result = stream.ToArray();
            }
            return _result;
        }
    }
}
